iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Mobile Development

Android 開發者養成計畫:從程式邏輯到作品集實戰系列 第 20

Day20- 第三階段挑戰:羅馬數字轉換 App (MVVM 準備)

  • 分享至 

  • xImage
  •  

在第十九天,你已經掌握了 LiveData 的終極武器,學會如何讓 View 自動觀察 ViewModel 的資料變化。你現在已經具備 MVVM 的思維了!

今天,我們就要將這個的能力,應用在一個程式解題的任務上:羅馬數字轉換器。透過這個專案,你會親自體會到,使用專業架構如何讓複雜的程式邏輯,變得更清晰、更好維護。

為什麼這個專案適合用 MVVM?

當我們在處理像羅馬數字轉換這樣的複雜邏輯時,如果所有的程式碼都混在 MainActivity.java 裡,會讓程式變得非常難以維護。

MVVM 就能完美解決這個問題。我們會將:

  • 介面邏輯:只留在 MainActivity (View)。
  • 轉換邏輯:移到 ViewModel (智慧助理) 裡面。
  • 資料本身:使用 LiveData 來包裝。

這樣,MainActivity 就只剩下最基本的「顯示」和「傳遞使用者輸入」的工作,變得非常輕薄。

MVVM 程式碼實作:羅馬數字轉換器 App

1. 修改你的 activity_main.xml

我們只需要一個 EditText、一個 Button 和一個 TextView

`<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/colorInput"
        android:layout_width="0dp"
        android-layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="32dp"
        android:hint="請輸入羅馬數字"
        android:minHeight="48dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/searchButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="轉換"
        app:layout_constraintEnd_toEndOf="@+id/colorInput"
        app:layout_constraintStart_toStartOf="@+id/colorInput"
        app:layout_constraintTop_toBottomOf="@+id/colorInput" />

    <TextView
        android:id="@+id/resultTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="轉換結果將顯示在這裡"
        android:textSize="20sp"
        app:layout_constraintEnd_toEndOf="@+id/searchButton"
        app:layout_constraintStart_toStartOf="@+id/searchButton"
        app:layout_constraintTop_toBottomOf="@+id/searchButton" />

</androidx.constraintlayout.widget.ConstraintLayout>`

2. 建立你的 ViewModel 類別

打開 MainActivity.java 所在的資料夾,右鍵點擊,選擇 New -> Java Class。給類別取名為 RomanConverterViewModel

  • 職責:它會負責處理羅馬數字轉換的所有邏輯,並使用 LiveData 來持有轉換後的結果。
`import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import java.util.HashMap;

public class RomanConverterViewModel extends ViewModel {

    // 建立一個 MutableLiveData 來持有轉換結果
    private final MutableLiveData<String> _result = new MutableLiveData<>();
    public LiveData<String> result = _result;

    // 羅馬數字與數值的對應表
    private final HashMap<Character, Integer> romanMap;

    public RomanConverterViewModel() {
        // 在 ViewModel 創建時,初始化羅馬數字字典
        romanMap = new HashMap<>();
        romanMap.put('I', 1);
        romanMap.put('V', 5);
        romanMap.put('X', 10);
        romanMap.put('L', 50);
        romanMap.put('C', 100);
        romanMap.put('D', 500);
        romanMap.put('M', 1000);
        _result.setValue(""); // 設定初始結果為空
    }

    // 這個方法是從 View 接收使用者輸入的入口
    public void convertRomanToInteger(String romanString) {
        if (romanString.isEmpty()) {
            _result.setValue("請輸入羅馬數字!");
            return;
        }

        try {
            int convertedValue = performConversion(romanString.toUpperCase());
            _result.setValue(String.valueOf(convertedValue)); // 成功後,更新 LiveData 的值
        } catch (IllegalArgumentException e) {
            _result.setValue("無效的羅馬數字!"); // 失敗時,更新 LiveData 的值
        }
    }

    // 羅馬數字轉換的邏輯,與之前相同
    private int performConversion(String s) {
        int result = 0;
        int previousValue = 0;

        for (int i = s.length() - 1; i >= 0; i--) {
            char currentChar = s.charAt(i);
            Integer currentValue = romanMap.get(currentChar);

            if (currentValue == null) {
                throw new IllegalArgumentException("無效的羅馬數字字符");
            }

            if (currentValue < previousValue) {
                result -= currentValue;
            } else {
                result += currentValue;
            }
            previousValue = currentValue;
        }
        return result;
    }
}`

3. 修改你的 MainActivity.java (View)

打開你的檔案 MainActivity.java。這次,Activity 只負責初始化,並設定 LiveData 的觀察者。

`import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.lifecycle.ViewModelProvider;

public class MainActivity extends AppCompatActivity {

    private RomanConverterViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 1. 取得 ViewModel 實例
        viewModel = new ViewModelProvider(this).get(RomanConverterViewModel.class);

        // 2. 找到 View 元件
        EditText romanInput = findViewById(R.id.colorInput);
        Button convertButton = findViewById(R.id.searchButton);
        TextView resultTextView = findViewById(R.id.resultTextView);

        // 3. 讓 View 開始「觀察」ViewModel 的結果
        viewModel.result.observe(this, newResult -> {
            // 當 ViewModel 的結果改變時,自動更新 TextView
            resultTextView.setText(newResult);
        });

        // 4. 設定按鈕點擊事件
        convertButton.setOnClickListener(v -> {
            // 將使用者輸入傳遞給 ViewModel 進行處理
            viewModel.convertRomanToInteger(romanInput.getText().toString());
        });
    }
}`

4. 執行你的 App!

  • 再次點擊綠色的「」按鈕,執行 App。
  • 試著在輸入框中輸入 IXLXXX 等羅馬數字。

day20

今日總結

今天我們成功地將 MVVM 架構應用在一個程式解題題目上,你學會了:

  • 如何將複雜的程式邏輯(羅馬數字轉換)從 Activity 中分離,並放入 ViewModel
  • 如何使用 LiveData,實現資料變動後,View 自動更新畫面的效果。
  • 體會到了 MVVM 如何讓你的 MainActivity 變得更輕薄、更專注於介面顯示。

從明天開始,我們將進入一個更進階的內容:Fragment,它是 MVVM 架構中不可或缺的一部分。

明天見!


上一篇
Day19- 讓畫面自動更新!認識 LiveData
下一篇
Day21- 模組化設計:深入理解 Fragment
系列文
Android 開發者養成計畫:從程式邏輯到作品集實戰22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言